﻿using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Net;

namespace HashFoo.Core
{
    /// <summary>
    /// Defines the data provided by a web request that results in stream access.
    /// </summary>
    public interface IWebResultStream : IDisposable
    {
        /// <summary>
        /// The consumable data returned in the response.
        /// </summary>
        Stream Data { get; }

        /// <summary>
        /// The response meta data details.
        /// </summary>
        WebResultDetails Details { get; }
    }

    /// <summary>
    /// A web request result, returned as a stream.  The caller should dispose of this object when done.
    /// </summary>
    public sealed class WebResultStream : WebResult<Stream>, IWebResultStream
    {
        readonly int _streamBufferSize;

        /// <summary>
        /// Create the result stream by wrapping the response.
        /// </summary>
        /// <param name="response"></param>
        /// <param name="buffer"></param>
        /// <param name="progressCallback"></param>
        /// <param name="streamBufferSize"></param>
        public WebResultStream(
            HttpWebResponse response, 
            bool buffer = true, 
            Action<ProgressEventArgs> progressCallback = null,
            int streamBufferSize = 1024 * 128)
            : base(response)
        {
            _streamBufferSize = streamBufferSize;

            if (buffer == false)
            {
                Data = response.GetResponseStream();
            }
            else
            {
                BufferStream(response, progressCallback);
            }
        }

        /// <summary>
        /// Read the stream into a buffer (MemoryStream).  Synchronous -- so hopefully this is being invoked on a worker thread.
        /// </summary>
        /// <param name="response"></param>
        /// <param name="progressCallback"></param>
        void BufferStream(HttpWebResponse response, Action<ProgressEventArgs> progressCallback = null)
        {
            var safeProgressCallback = progressCallback ?? (args => { });
            var readSize = _streamBufferSize;

            using (var networkStream = response.GetResponseStream())
            {
                var contentLength = (int)response.ContentLength;
                var length = contentLength > 0 ? contentLength : 0;
                var totalIsKnown = length != 0;

                Data = new MemoryStream(length);

                var buffer = new byte[readSize];
                int readCount;
                var currentPosition = 0;

                while ((readCount = networkStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    Data.Write(buffer, 0, readCount);
                    currentPosition += readCount;

                    safeProgressCallback(new ProgressEventArgs
                    {
                        Current = currentPosition,
                        Percent = totalIsKnown ? (int)(Math.Round((float)currentPosition / length, 2) * 100) : 0,
                        Total = length,
                        TotalIsKnown = totalIsKnown
                    });
                }

                safeProgressCallback(new ProgressEventArgs
                {
                    Current = length,
                    Percent = 100,
                    Total = length,
                    TotalIsKnown = totalIsKnown
                });

                Data.Seek(0, SeekOrigin.Begin);
            }
        }

        /// <summary>
        /// Clean up the response stream before the response is disposed.
        /// </summary>
        protected override void BeforeResponseDisposed()
        {
            // clean up the response stream, if we have it still.

            if (Data == null) return;

            Data.Dispose();
            Data = null;
        }
    }
}